﻿using System;

using System.Collections.Generic;
using System.Text;
using Silvermoon.Controls.Base;
using Silvermoon.Controls.Interfaces;
using Silvermoon.Core;
using System.Drawing;

namespace Silvermoon.Controls
{
    /// <summary>
    /// Determines how the controls are layouted inside StackPanel.
    /// </summary>
    public enum StackPanelOrientation : short
    {
        /// <summary>
        /// Controls are layouted vertically.
        /// </summary>
        Vertical,

        /// <summary>
        /// Controls are layouted horizontally.
        /// </summary>
        Horizontal
    }

    /// <summary>
    /// Determines how the controls are layouted inside StackPanel.
    /// </summary>
    public enum StackPanelAlignment : short
    {
        /// <summary>
        /// Each control has the same width or height which is the fraction of the panels size and the number of controls.
        /// The exact placement of each controls is determined by the control's margin.
        /// </summary>
        Stretched,

        /// <summary>
        /// Each control are layouted in sequence with individual width or height.
        /// </summary>
        Dense
    }

    /// <summary>
    /// Layouts a collection of controls either vertically or horizontally stacked, with 3 options:
    /// a) StackPanelAlignment.Dense lets each control specify it's desired height or width.
    /// b) StackPanelAlignment.Stretched each control gets the same height or width which depends on the size of the stack panel divided by the number of controls.
    /// c) if FillControlIndex is set to an index of a control, the behavior follows rule a) except that the control with the given index obtains the remaining size of the stack panel.
    /// StackPanel does not support overflow scrolling. For this case use <see cref="ScrollStackPanel"/>.
    /// </summary>
    public class StackPanel : CompositeControlBase, INotifyControlCollection
    {
        private int fillControlIndex = -1;
        private bool isLayouting = false;
        private StackPanelOrientation orientation;
        private StackPanelAlignment alignment;

        public StackPanelAlignment Alignment
        {
            get { return alignment; }
            set
            {
                if (alignment != value)
                {
                    alignment = value;
                    OnAlignmentChanged();
                }
            }
        }

        public StackPanelOrientation Orientation
        {
            get { return orientation; }
            set
            {
                if (orientation != value)
                {
                    orientation = value;
                    OnOrientationChanged();
                }
            }
        }

        public ControlCollection Controls { get { return GetVisibleControls(); } }

        /// <summary>
        /// Gets or sets the index of the control which fills the remaining space, otherwise -1.
        /// If set to an index of a control, the layout is affected in that way, that every control except that indexed control determines it's height by itself, while the
        /// indexed control is assigned to the remaining space in the stack panel.
        /// </summary>
        public int FillControlIndex
        {
            get { return fillControlIndex; }
            set
            {
                if (value < -1) value = -1;
                if (fillControlIndex != value)
                {
                    this.fillControlIndex = value;
                    Invalidate(InvalidationFlag.Layout);
                }
            }
        }

        public int ControlCount { get; private set; }

        protected override void OnLayout()
        {
            base.OnLayout();
            if (CheckFlag(InvalidationFlag.Layout | InvalidationFlag.Size))
            {
                if (Width > 0 && Height > 0)
                {
                    InvalidationMask = 0;
                    isLayouting = true;
                    if (orientation == StackPanelOrientation.Horizontal)
                    {
                        if (fillControlIndex >= 0) LayoutFilledHorizontal();
                        else LayoutHorizontal();
                    }
                    else
                    {
                        if (fillControlIndex >= 0) LayoutFilled();
                        else LayoutVertical();
                    }
                    isLayouting = false;
                }
            }
        }

        protected virtual void LayoutFilledHorizontal()
        {
            int n = ControlCount;
            if (n > 0)
            {
                int index = 0;
                Control filledControl = null;
                int w = ClientWidth;
                int h = ClientHeight;
                foreach (Control c in VisibleControls)
                {
                    if (c.Visible)
                    {
                        if (index == fillControlIndex) filledControl = c;
                        else
                        {
                            Rectangle r = c.Margin.Layout(c.Bounds, new Size(c.PaddingWidth, h));
                            c.Size = r.Size;
                            c.PreLoad();
                            w -= c.PaddingWidth;
                        }
                    }
                    index++;
                }
                int top = ClientBounds.Top;
                int left = ClientBounds.Left;
                int w_fill = Math.Max(0, w);
                foreach (Control c in VisibleControls)
                {
                    if (!c.Visible) continue;
                    c.Location = new Point(left, top);
                    if (c == filledControl)
                    {
                        c.Size = new Size(w_fill - c.Margin.Width, h);
                        c.PreLoad();
                    }
                    Rectangle bounds = c.Margin.Layout(c.Bounds, new Size(c.PaddingWidth, h));
                    bounds.Offset(left, top);
                    c.Bounds = bounds;
                    left += c.PaddingWidth;
                }
            }
        }


        protected virtual void LayoutFilled()
        {
            int n = ControlCount;
            if (n > 0)
            {
                int index = 0;
                Control filledControl = null;
                int w = ClientWidth;
                int h = ClientHeight;
                foreach (Control c in VisibleControls)
                {
                    if (c.Visible)
                    {
                        if (index == fillControlIndex) filledControl = c;
                        else
                        {
                            Rectangle r = c.Margin.Layout(c.Bounds, new Size(w, c.PaddingHeight));
                            c.Size = r.Size;
                            c.PreLoad();
                            h -= c.PaddingHeight;
                        }
                    }
                    index++;
                }
                int top = ClientBounds.Top;
                int left = ClientBounds.Left;
                int h_fill = Math.Max(0, h);
                foreach (Control c in VisibleControls)
                {
                    if (!c.Visible) continue;
                    c.Location = new Point(left, top);
                    if (c == filledControl)
                    {
                        c.Size = new Size(w, h_fill - c.Margin.Height);
                    }
                    Rectangle bounds = c.Margin.Layout(c.Bounds, new Size(w, c.PaddingHeight));
                    bounds.Offset(left, top);
                    c.Bounds = bounds;

                    //                    c.Bounds = c.Margin.LayoutChild(new Rectangle(left, top, w, c.PaddingHeight));
                    top += c.PaddingHeight;
                }
            }
        }

        protected virtual void LayoutVertical()
        {
            int n = ControlCount;
            if (n > 0)
            {
                int tileHeight = Height / n;
                int top = (Height - tileHeight * n) / 2;
                Control c = VisibleControls.First;
                int w = Width;
                Size size = new Size(w, tileHeight);
                bool isDense = alignment == StackPanelAlignment.Dense;
                while (c != null)
                {
                    Margin m = c.Margin;
                    if (isDense)
                    {
                        tileHeight = c.PaddingHeight;
                        size = new Size(w, tileHeight);
                    }

                    Rectangle bounds = new Rectangle(0, 0, c.Width, c.Height);
                    bounds = m.Layout(bounds, size);
                    bounds.Y += top;
                    c.Bounds = bounds;
                    top += c.PaddingHeight;
                    c = c.NextControl;
                }
            }
        }

        protected virtual void LayoutHorizontal()
        {
            int n = ControlCount;
            if (n > 0)
            {
                int tileWidth = Width / n;
                int left = (Width - tileWidth * n) / 2;
                int h = Height;
                Control c = VisibleControls.First;
                Size size = new Size(tileWidth, h);
                bool isDense = alignment == StackPanelAlignment.Dense;
                while (c != null)
                {
                    if (isDense)
                    {
                        tileWidth = c.Width;
                        size = new Size(tileWidth, h);
                    }
                    Rectangle bounds = new Rectangle(0, 0, c.Width, c.Height);
                    Margin m = c.Margin;
                    if (m != null)
                    {
                        bounds = m.Layout(bounds, size);
                        bounds.X += left;
                        c.Bounds = bounds;
                    }
                    c = c.NextControl;
                    left += tileWidth;
                }
            }
        }

        protected virtual void OnAlignmentChanged()
        {
            Invalidate(InvalidationFlag.Layout);
        }

        protected override void OnChildSizeChanged(Control child, Size oldSize)
        {
            if (!isLayouting)
            {
                switch (orientation)
                {
                    case StackPanelOrientation.Vertical:
                        if (child.Height != oldSize.Height) Invalidate(InvalidationFlag.Layout);
                        break;

                    case StackPanelOrientation.Horizontal:
                        if (child.Width != oldSize.Width) Invalidate(InvalidationFlag.Layout);
                        break;
                }
            }
        }

        protected override void OnChildVisibleChanged(Control child)
        {
            Invalidate(InvalidationFlag.Layout);
        }


        protected virtual void OnOrientationChanged()
        {
            Invalidate(InvalidationFlag.Layout);
        }

        #region INotifyControlCollection Members

        void INotifyControlCollection.ControlAdded(Control c)
        {
            ControlCount++;
            Invalidate(InvalidationFlag.Layout);
        }

        void INotifyControlCollection.CollectionCleared()
        {
            ControlCount = 0;
            Invalidate(InvalidationFlag.Layout);
        }

        void INotifyControlCollection.ControlRemoved(Control c)
        {
            --ControlCount;
            Invalidate(InvalidationFlag.Layout);
        }

        #endregion
    }
}
